package ngat.oss.simulation;

import ngat.phase2.*;
import ngat.util.*;

import java.util.*;
import java.text.*;

/** A simple mutable implementation of WeatherPredictor.*/
public class StandardWeatherPredictor implements WeatherModel, PropertiesConfigurable {

    private double meanInterval;
    private double sigmaInterval;
    private double minInterval;

    private double meanLength;
    private double sigmaLength;
    private double minLength;

    private SortedSet weatherProfile;

    private GaussianRandom gwin;
    private GaussianRandom gint;

    private boolean startBad = false;
    private long startTime = 0L;
    private long endTime = 0L;

    /** Create a StandardWeatherPredictor.*/
    public StandardWeatherPredictor() {
	weatherProfile = new TreeSet();
    }

    /** Configure the predictor.*/
    public void configure(ConfigurationProperties config) throws Exception {

	// bad weather interval distribution (hours).
	meanInterval  = config.getDoubleValue("mean.interval");
	sigmaInterval = config.getDoubleValue("sigma.interval");
	minInterval   = config.getDoubleValue("min.interval");

	// bad weather length distribution (hours).
	meanLength  = config.getDoubleValue("mean.length");
	sigmaLength = config.getDoubleValue("sigma.length");
	minLength   = config.getDoubleValue("min.length");

	gint = new GaussianRandom(meanInterval, sigmaInterval, minInterval);
	gwin = new GaussianRandom(meanLength,   sigmaLength,   minLength);

    }
    
    /** Precompute the weather from start date to end date.*/
    public void preCompute(long start, long end) {

	double pbad = meanLength/(meanLength + meanInterval);

	double rbad = Math.random();
	if (rbad < pbad) 
	    startBad = true;
	else
	    startBad = false;
	
	System.err.println("Starting "+(startBad ? "BAD": "GOOD")+" at "+ScheduleSimulator.sdf.format(new Date(startTime)));
	
	// we are this far into the initial GOOD or BAD period 
	double rmid = Math.random();
	boolean nextIsBad = false;

	long endFirst = 0L;
	if (startBad) {
	    double s1  = 3600000.0*gwin.random();      // length of initial bad window
	    long s1pre  = start - (long)(rmid*s1);       // when did it start.
	    long s1post = start + (long)((1.0-rmid)*s1); // when does it end.
	    startTime = s1pre;
	    WeatherSnapshot wbad = new WeatherSnapshot();
	    wbad.good = false;
	    weatherProfile.add(new WeatherProfileEntry(s1pre, s1post, wbad));
	    System.err.println("Starting  BAD at: "+ScheduleSimulator.sdf.format(new Date(startTime)));
	    System.err.println("BAD   start till: "+ScheduleSimulator.sdf.format(new Date(s1post)));
	    endFirst = s1post;
	    nextIsBad = false;
	} else {
	    double s1 = 3600000.0*gint.random();       // length of initial good window
	    long s1pre  = start - (long)(rmid*s1);       // when did it start.
	    long s1post = start + (long)((1.0-rmid)*s1); // when does it end.
	    startTime = s1pre;
	    WeatherSnapshot wgood = new WeatherSnapshot();
	    wgood.good = true;
	    weatherProfile.add(new WeatherProfileEntry(s1pre, s1post, wgood));  
	    System.err.println("Starting GOOD at: "+ScheduleSimulator.sdf.format(new Date(startTime)));
	    System.err.println("GOOD  start till: "+ScheduleSimulator.sdf.format(new Date(s1post)));
	    endFirst = s1post;
	    nextIsBad = true;
	}

	long t = endFirst;
	endTime = t;
	while (t < end) {
	    if (nextIsBad) {
		// doing a BAD window
		long lbad = (long)(3600000.0*gwin.random());
		WeatherSnapshot wbad = new WeatherSnapshot();
		wbad.good = false;
		weatherProfile.add(new WeatherProfileEntry(t, t+lbad, wbad));
		t += lbad;
		System.err.println("BAD  window till: "+ScheduleSimulator.sdf.format(new Date(t)));
		nextIsBad = false;
	    } else {
		// doing a GOOD window
		long lgood = (long)(3600000.0*gint.random());
		WeatherSnapshot wgood = new WeatherSnapshot();
		wgood.good = true;
		weatherProfile.add(new WeatherProfileEntry(t, t+lgood, wgood));
		t += lgood;
		System.err.println("GOOD window till: "+ScheduleSimulator.sdf.format(new Date(t)));
		nextIsBad = true;
	    }	    
	    endTime = t;
	}
	System.err.println("Ending "+(startBad ? "BAD": "GOOD")+" at "+ScheduleSimulator.sdf.format(new Date(endTime)));
    }

    /** Returns true if the weather at time is good.*/
    public WeatherSnapshot predictWeather(long time) {

	WeatherSnapshot wr = new WeatherSnapshot();
	
	// these are for times before the first possible time
	double pbad = meanLength/(meanLength + meanInterval);
	double rbad = Math.random();
	if (rbad < pbad) 
	    wr.good = false;
	else
	    wr.good = true;

	// the weather should have been pre-populated so we can just read it out from here.
	if (time < startTime) {	   
	    System.err.println("At "+ScheduleSimulator.sdf.format(new Date(time))+" is Before start");	
	    return wr;
	}

	if (time > endTime) {
	    System.err.println("At "+ScheduleSimulator.sdf.format(new Date(time))+" is After end");	 
	    return wr;
	}

	Iterator it = weatherProfile.iterator();
	while (it.hasNext()) {
	    WeatherProfileEntry wpe = (WeatherProfileEntry)it.next();
	    
	    long            st = wpe.stime;
	    long            et = wpe.etime;
	    WeatherSnapshot w = wpe.weather;
	    //System.err.println("Check Profile for: "+ScheduleSimulator.sdf.format(new Date(time))+
	    //	       " entry for: "+
	    //	       ScheduleSimulator.sdf.format(new Date(st))+" to "+
	    //	       ScheduleSimulator.sdf.format(new Date(et))+
	    //	       " WG="+w.good);
	    
	    // t is the end of that weather slot, the setting is from last one we saw
	    if (time >= st && time < et) {
		wr = w;
		break;
	    } 

	}
	
	return wr;
    }

  private class WeatherProfileEntry implements Comparable {

	public long stime;
	public long etime;
	public WeatherSnapshot weather;

	private WeatherProfileEntry(long stime, long etime, WeatherSnapshot weather) {
	    this.stime = stime;
	    this.etime = etime;
	    this.weather = weather;
	}

	public int compareTo(Object o) {
	 
	    WeatherProfileEntry w = (WeatherProfileEntry)o;
	 
	    if (stime < w.stime)
		return -1;
	    if (stime > w.stime)
		return 1;
	    return 0;

	}

    }


}
